﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using ClientResource.Scripts.Core;
using Google.Protobuf;

namespace Pisces
{
	/// <summary>
	/// 
	/// created by wangqiang
	/// </summary>
	public class TcpChannel
	{
		/// <summary>
		/// 网络链接的socket
		/// </summary>
		private Socket socket;

		/// <summary>
		/// 对端的地址
		/// </summary>
		private string host;

		/// <summary>
		/// 对端的端口
		/// </summary>
		private int port;

		/// <summary>
		/// socket的连接状态
		/// </summary>
		private SocketConnectionState connectionState;
		
		/// <summary>
		/// protobuf协议的消息接收队列
		/// </summary>
		private Queue<ProtobufPacket> protobufReceiveQueue;

		/// <summary>
		/// 接收消息的缓存
		/// </summary>
		private ByteBuffer receiveByteBuffer;

		/// <summary>
		/// 发送的字节流队列
		/// </summary>
		private Queue<ByteBuffer> sendByteQueue;

		/// <summary>
		/// 消息解析器
		/// </summary>
		private IProtocolParser protocolParser;
		
		public void onAwake()
		{
			protobufReceiveQueue = new Queue<ProtobufPacket>();
			connectionState = SocketConnectionState.DISCONNECTED;
			sendByteQueue = new Queue<ByteBuffer>();
			
			// 大小为16k
			receiveByteBuffer = new ByteBuffer(16 * 1024);
		}

		public void onUpdate(float deltaTime)
		{
			tickReceiveQueue(deltaTime);
		}

		public void tickReceiveQueue(float deltaTime)
		{
			if (protobufReceiveQueue == null)
			{
				return;
			}

			int loopCount = 0;
			lock (protobufReceiveQueue)
			{
				loopCount = protobufReceiveQueue.Count;
			}

			loopCount = Math.Max(loopCount, 128);
			for (int i = 0; i < loopCount; i++)
			{
				ProtobufPacket protobufPacket;
				lock (protobufReceiveQueue)
				{
					if (protobufReceiveQueue.Count == 0)
					{
						break;
					}
					protobufPacket = protobufReceiveQueue.Dequeue();
				}
				
				if (protobufPacket == null)
				{
					continue;
				}

				if (protocolParser == null)
				{
					PiscesLog.error($"channel处理消息:{protobufPacket.packetID}时,解析器为空, host:{this.host}, ip:{this.port}");
					break;
				}

				try
				{
					// 解析协议
					IMessage message = protocolParser.parseProtobufPacket(protobufPacket.packetID, protobufPacket.data);
					if (message == null)
					{
						PiscesLog.error($"channel消息:{protobufPacket.packetID}解析出的内容为空, host:{this.host}, ip:{this.port}");
						continue;
					}
					
					PiscesLog.debug($"gateway收到消息，处理消息 {protobufPacket.packetID}:{message}");
					
					// 消息处理
					TcpEventModule.getSingleton().trigger(protobufPacket.packetID, message);
				}
				catch (Exception e)
				{
					PiscesLog.error($"解析protobuf协议并处理时发生异常,packetID:{protobufPacket.packetID} e:{e}");
					continue;
				}
				
			}
		}

		public void onDestroy()
		{
			closeSocket();
		}

		public int connectToGateway(string host, int port, IProtocolParser parser)
		{
			// 构建socket使用的地址对象
			try
			{
				if (socket != null)
				{
					PiscesLog.warn("当前已经存在socket连接");
					return (int)CoreErrorCode.SOCKET_IS_EXISTED;
				}
				
				IPAddress ia = IPAddress.Parse(host);
				if (isIpV4(host))
				{
					socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
				}
				else
				{
					socket = new Socket(ia.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
				}

				this.host = host;
				this.port = port;
				this.protocolParser = parser;
				connectionState = SocketConnectionState.CONNECTING;
				
				PiscesLog.info($"开始建立连接{host}:{port}");
				socket.BeginConnect(host, port, onConnectCallback, socket);
			}
			catch (Exception e)
			{
				PiscesLog.error($"连接gateway {host}:{port},出现异常,e:{e}");
				return (int)CoreErrorCode.SOCKET_CONNECTION_FAIL;
			}

			return 0;
		}
		
		private void onConnectCallback(IAsyncResult result)
		{
			try
			{
				Socket so = result.AsyncState as Socket;
				if (so == null)
				{
					connectionState = SocketConnectionState.CONNECT_FAILED;
					PiscesLog.warn($"socket连接失败, so:{so.RemoteEndPoint}");
					return;
				}

				if (so.Connected)
				{
					so.EndConnect(result);
					connectionState = SocketConnectionState.CONNECTED;
					
					PiscesLog.info($"连接gateway成功, host:{host}, port:{port}");
					socket.BeginReceive(receiveByteBuffer.getBuffer(), 0, receiveByteBuffer.Capacity, 0, onBeginReceiveCallback, socket);
				}
				else
				{
					PiscesLog.error($"连接gateway失败, host:{host}, port:{port}");
					// TODO 发送事件
				}
			}
			catch (Exception e)
			{
				PiscesLog.error($"连接回调发生异常，e:{e}");
			}
		}

		private void onBeginReceiveCallback(IAsyncResult result)
		{
			try
			{
				var receiveSize = socket.EndReceive(result);
				
				if (receiveSize <= 0)
				{
					closeSocket();
					// TODO 抛事件，上层处理
					PiscesLog.warn($"socket 接收消息回调中，size:{receiveSize}小于等于0，网络断开");
					return;
				}

				receiveByteBuffer.Offset += receiveSize;

				int length = receiveByteBuffer.Offset;

				// 没有接收完消息头
				if (length < PacketHeaderDefine.TCP_PROTOBUF_HEADER_SIZE)
				{
					// 继续接收
					socket.BeginReceive(receiveByteBuffer.getBuffer(), receiveByteBuffer.Offset, receiveByteBuffer.Capacity - length, 
						SocketFlags.None, onBeginReceiveCallback, socket);
					return;
				}
				
				int packetLength = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(receiveByteBuffer.getBuffer(), 0));
				short packetID = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(receiveByteBuffer.getBuffer(), 4));
				
				// 是否收齐了整包
				if (length < packetLength + PacketHeaderDefine.TCP_PROTOBUF_HEADER_SIZE)
				{
					// 继续接收
					socket.BeginReceive(receiveByteBuffer.getBuffer(), receiveByteBuffer.Offset, receiveByteBuffer.Capacity - length, 
						SocketFlags.None, onBeginReceiveCallback, socket);
					return;
				}
			
				// 收齐了整包，开始处理数据
				handleReceivedData(packetID, packetLength);
			
				socket.BeginReceive(receiveByteBuffer.getBuffer(), receiveByteBuffer.Offset, receiveByteBuffer.Capacity, 
					SocketFlags.None, onBeginReceiveCallback, socket);
			}
			catch (SocketException ex)
			{
				// Socket 异常
				PiscesLog.error($"接收消息时socket异常, e:{ex}");
				closeSocket();
				// TODO 抛事件，上层处理
			}
			catch (ObjectDisposedException ex)
			{
				PiscesLog.error(($"接收消息时socket异常, e:{ex}"));
				closeSocket();
				// TODO 抛事件，上层处理
			}
			catch (Exception ex)
			{
				PiscesLog.error($"接收消息时socket发生未知异常, e:{ex}");
				closeSocket();
				// TODO 抛事件，上层处理
			}
		}

		private void closeSocket()
		{
			PiscesLog.info($"关闭socket host:{this.host}, port:{this.port}");
			connectionState = SocketConnectionState.DISCONNECTED;
			
			if (protobufReceiveQueue != null)
			{
				protobufReceiveQueue.Clear();
			}

			if (sendByteQueue != null)
			{
				sendByteQueue.Clear();
			}

			if (receiveByteBuffer != null)
			{
				receiveByteBuffer.clear();
			}
			
			if (socket != null)
			{
				socket.Shutdown(SocketShutdown.Both);
				socket.Close();
				socket = null;
			}
		}

		/// <summary>
		/// 处理接收单包数据
		/// </summary>
		/// <param name="packetID"></param>
		/// <param name="packetLength"></param>
		private void handleReceivedData(short packetID, int packetLength)
		{
			if (receiveByteBuffer == null)
			{
				return;
			}
			
			byte[] data = new byte[packetLength];
			Array.Copy(receiveByteBuffer.getBuffer(), PacketHeaderDefine.TCP_PROTOBUF_HEADER_SIZE, data, 0, packetLength);
						
			ProtobufPacket packet = new ProtobufPacket();
			packet.packetID = packetID;
			packet.data = data;

			lock (protobufReceiveQueue)
			{
				protobufReceiveQueue.Enqueue(packet);
			}
			
			// 将receiveByteBuffer内的buffer的剩余数据挪到前面
			int length = receiveByteBuffer.Offset;
			int needCopyLength = length - (PacketHeaderDefine.TCP_PROTOBUF_HEADER_SIZE + packetLength);
			if (needCopyLength < 0)
			{
				PiscesLog.error($"处理tcp消息buffer时出错，需要拷贝的长度:{needCopyLength}小于0, current length:{length}," +
				                $" capacity:{receiveByteBuffer.Capacity}, bytes length:{receiveByteBuffer.getLength()}");
				receiveByteBuffer.clear();
			}
			else if (needCopyLength > 0)
			{
				// TODO 优化成大中小的缓存
				byte[] tempBuffer = new byte[needCopyLength];
				Array.Copy(receiveByteBuffer.getBuffer(), (PacketHeaderDefine.TCP_PROTOBUF_HEADER_SIZE + packetLength), 
					tempBuffer, 0, needCopyLength);
				receiveByteBuffer.clear();
				Array.Copy(tempBuffer, 0, receiveByteBuffer.getBuffer(), 0, needCopyLength);
			}
			else
			{
				receiveByteBuffer.clear();
			}

		}

		/// <summary>
		/// 发送protobuf协议的消息
		/// </summary>
		/// <param name="packetID"></param>
		/// <param name="packet"></param>
		public void sendProtobufPacket(short packetID, IMessage packet)
		{
			// 发送packet内的数据到socket,其中消息头是2字节的消息id，4自己的消息体长度，消息体是ProtobufPacket中的byte数组
			if (socket == null || socket.Connected == false)
			{
				PiscesLog.error($"socket未连接，无法发送消息, packetID:{packetID}");
				return;
			}

			byte[] body = packet.ToByteArray();
			ByteBuffer buffer = new ByteBuffer(PacketHeaderDefine.TCP_PROTOBUF_HEADER_SIZE + body.Length);

			try
			{ 
				
				byte[] packetIDBytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(packetID));
				byte[] packetLengthBytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(body.Length));
				
				Array.Copy(packetLengthBytes, 0, buffer.getBuffer(), 0, PacketHeaderDefine.TCP_PROTOBUF_HEADER_BODY_LENGTH_SIZE);
				Array.Copy(packetIDBytes, 0, buffer.getBuffer(), PacketHeaderDefine.TCP_PROTOBUF_HEADER_BODY_LENGTH_SIZE, PacketHeaderDefine.TCP_PROTOBUF_HEADER_ID_SIZE);
				Array.Copy(body, 0, buffer.getBuffer(), PacketHeaderDefine.TCP_PROTOBUF_HEADER_SIZE, body.Length);

				lock (sendByteQueue)
				{
					sendByteQueue.Enqueue(buffer);
					// 不是第一个发送的，等待上次的发送完成，会在发送回调中处理，继续从queue中拿消息发送
					if (sendByteQueue.Count == 1)
					{
						socket.BeginSend(buffer.getBuffer(), 0, buffer.getBuffer().Length, SocketFlags.None, onSendProtobufCallback, socket);
					}
				}
				PiscesLog.debug($"向gateway发送消息, {packetID}:{packet}");
			}
			catch (Exception e)
			{
				PiscesLog.error($"发送消息{packetID}:{packet}时发生未知异常, e:{e}");
			}
		}

		private void onSendProtobufCallback(IAsyncResult ar)
		{
			Socket so = ar.AsyncState as Socket;
			
			try
			{
				int sendSize = so.EndSend(ar);

				if (sendSize <= 0)
				{
					closeSocket();
					PiscesLog.warn($"socket 发送消息回调中，size:{sendSize}小于0");
					return;
				}

				ByteBuffer byteBuffer;
				lock (sendByteQueue)
				{
					if (sendByteQueue.Count == 0)
					{
						PiscesLog.debug("没有需要发送的网络消息了");
						return;
					}
					byteBuffer = sendByteQueue.Peek();
					if (byteBuffer == null)
					{
						sendByteQueue.Dequeue();
						PiscesLog.warn($"发送消息队列中，取出的buffer为空");
						return;
					}
				}

				byteBuffer.Offset += sendSize;
				
				// 是否发送完了整包
				if (byteBuffer.Offset == byteBuffer.getBuffer().Length)
				{
					lock (sendByteQueue)
					{
						sendByteQueue.Dequeue();

						// 后面还有包，继续发送
						if (sendByteQueue.Count > 0)
						{
							ByteBuffer buffer = sendByteQueue.Peek();
							if (buffer != null)
							{
								socket.BeginSend(buffer.getBuffer(), 0, buffer.getBuffer().Length, SocketFlags.None, onSendProtobufCallback, socket);
								return;
							}
						}
					}
				}
				else if (byteBuffer.Offset > byteBuffer.getBuffer().Length)
				{
					// 出错了
					PiscesLog.error($"发送消息错误，发送的长度:{byteBuffer.Offset}大于缓冲区长度:{byteBuffer.getBuffer().Length}");
					lock (sendByteQueue)
					{
						sendByteQueue.Dequeue();
                    
						// 这个包不放松了，继续发送后续的包
						if (sendByteQueue.Count > 0)
						{
							ByteBuffer buffer = sendByteQueue.Peek();
							if (buffer != null)
							{
								socket.BeginSend(buffer.getBuffer(), 0, buffer.getBuffer().Length, SocketFlags.None, onSendProtobufCallback, socket);
								return;
							}
						}
					}
				}
				else
				{
					// 没有发送完，继续发送
					so.BeginSend(byteBuffer.getBuffer(), byteBuffer.Offset, byteBuffer.getBuffer().Length - byteBuffer.Offset, SocketFlags.None, onSendProtobufCallback, so);
					return;
				}
				
			}
			catch (SocketException ex)
			{
				// Socket 异常
				PiscesLog.error($"发送消息 socket异常, e:{ex}");
				closeSocket();
				// TODO 抛事件，上层处理
			}
			catch (ObjectDisposedException ex)
			{
				PiscesLog.error(($"发送消息 socket异常, e:{ex}"));
				closeSocket();
				// TODO 抛事件，上层处理
			}
			catch (Exception ex)
			{
				PiscesLog.error($"发送消息 socket发生未知异常, e:{ex}");
				closeSocket();
				// TODO 抛事件，上层处理
			}
			
		}

		// /// <summary>
		// /// 是否为IPV4
		// /// </summary>
		private bool isIpV4(string ip)
		{
			IPAddress[] localIPs = Dns.GetHostAddresses(ip);
			foreach (IPAddress hostip in localIPs)
			{
				if (hostip.AddressFamily == AddressFamily.InterNetwork)
				{
					return true;
				}
			}

			return false;
		}
	}
	
	
}